#ifndef vmidi_rtmidi_h
#define vmidi_rtmidi_h

#include "vmidi.h"
#include "vmidi_impl_common.h"
#include "RtMidi.h"
#include <pthread.h>
#include <set>
using namespace std;

namespace VMidi {

/**
  * Callback used when receiving MIDI IN events.
  * This is the entry point that will dispatch to specific callback
  * (because we cannot pass the address of a member to RtMidiIn::setCallback()).
  */
void rtMidiCallback (double timeStamp, std::vector< unsigned char > *message, void
        *userData);

/**
  * @class VMidiRtMidiThruCallback
  * @brief Callback class used when setting up RtMidi callbacks.
  *        It takes a list of output port as an argument so that
  *        MIDI messages are redirected to each one of them (THRU).
  */
class VMidiRtMidiThruCallback {
  public:

    /**
     * @brief Constructs a new MIDI THRU callback, based
     *        on a set of outputs.
     *        The set guaranties that the signal will never
     *        be sent to the same port more than once.
     */
    VMidiRtMidiThruCallback (set<RtMidiOut*>& out) : out_(out) {
      pthread_mutex_init(&mutex, NULL);
    }

    void callback (double timeStamp, std::vector< unsigned char > *message, void
        *userData);

    void addOutput (RtMidiOut* out);

    void removeOutput (RtMidiOut* out);

  private:
    set<RtMidiOut*>& out_;
    pthread_mutex_t mutex;
};

/**
  * @class VMidiRtMidi
  * @brief Public interface to VMidi
  */
class VMidiRtMidi: public VMidiImplCommon {
  public:
    /**
      * Initializes the VMidi system.
      * This function will open the underlying MIDI API and probe for
      * available MIDI ports.
      * @throw An error string in case of error in the implementation.
      */
    void initialize () throw(string) {
      // Nothing to initialize.
    }
    
    /**
      * Creates a new virtual port.
      * @param type Type of port (input or output).
      * @param name Name of the port. It is not necessarily unique.
      * @param portId (identifier of the newly created port).
      * @throw An error string in case of error in the implementation.
      * @sa VMidiPortType
      */
    void createVirtualPort (VMidiPortType type, const string& name, int& portId)
    throw(string);

    /**
     * Destroys a virtual port previously created with @sa createVirtualPort.
     * Note that it's impossible to destroy ports existing outside VMidi.
     * @param type Type of port (input or output).
     * @param portId identifier of the port to destroy.
     * @throw An error string in case of error in the implementation.
     */
    void destroyVirtualPort (VMidiPortType type, int portId) throw(string);

    /**
      * Finalizes the VMidi system.
      * This function will close the underlying MIDI API and free the
      * list of declared MIDI ports (both real and virtual).
      * @throw An error string in case of error in the implementation.
      */
    void finalize () throw(string) {}

    /**
      * Connects an input port to an output port.
      * After the connection is established, every information coming into
      * @p srcPortId will be redirected to port @dstPortId. This is
      * called MIDI Thru.
      * @param srcPortId source port identifier.
      * @param dstPortId destination port identifier.
      * @throw An error string in case of error in the implementation.
     */
    virtual void connectPorts (int srcPortId, int dstPortId) throw(string);

    /**
     * Disconnects the provided ports so that VMidi will not perform any MIDI
     * thru between them anymore.
     * @throw An error string in case of error in the implementation.
     */
    virtual void disconnectPorts (int srcPortId, int dstPortId) throw(string);

  private:
    void destroyCallback (int inputPortId) {
      map<int, VMidiRtMidiThruCallback*>::iterator it = callbacks.find(inputPortId);

      if (it != callbacks.end()) {
        VMidiRtMidiThruCallback* cb = it->second;
        delete cb;
        callbacks.erase(it);
      }
    }

  public:
    VMidiRtMidi()  {}
    ~VMidiRtMidi() {
      // Destroy all the callbacks.
      for (map<int, VMidiRtMidiThruCallback*>::iterator it = callbacks.begin();
           it != callbacks.end();
           ++it) {
        delete it->second;
      }
    }


  private:
    // Maps an input to a callback.
    map<int, VMidiRtMidiThruCallback*> callbacks;
};

};

#endif

